package com.mf.service.impl;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mf.bo.CodeDateScopedBo;
import com.mf.bo.DateScopedBo;
import com.mf.bo.calculateHistory.CalculateHistoryBo;
import com.mf.bo.calculateHistory.ProfitGatherBo;
import com.mf.bo.calculateHistory.SumCalculateHistoryBo;
import com.mf.config.GlobalParameter;
import com.mf.entity.Code;
import com.mf.entity.CodeHistory;
import com.mf.entity.Positions;
import com.mf.exceptions.GraceException;
import com.mf.mapper.CodeHistoryMapper;
import com.mf.service.ICodeHistoryService;
import com.mf.utils.DateUtil;
import com.mf.utils.StockGatherUtils;
import com.mf.service.utils.numericalProcedure.BasicNumerical;
import com.mf.service.utils.numericalProcedure.BasicParams;
import com.mf.service.utils.numericalProcedure.CalculateProcedure.CalculateProcedure;
import com.mf.service.utils.numericalProcedure.NumericalProcedure1;
import com.mf.vo.StockGatherVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 股票推荐表 服务实现类
 * </p>
 *
 * @author mf
 * @since 2024-06-02
 */
@Service
@Slf4j
@DS("code_history")
public class CodeHistoryServiceImpl extends ServiceImpl<CodeHistoryMapper, CodeHistory> implements ICodeHistoryService {
    @Resource
    private CodeHistoryMapper codeHistoryMapper;

    @Override
    @Transactional
    public List<CodeHistory> getCodeHistoriesByCodeDateScoped(CodeDateScopedBo dateScopedBo1) {
        // 定义日期格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 解析日期字符串为LocalDate对象
        LocalDate targetDate = LocalDate.parse(dateScopedBo1.getStartDate(), formatter);
        // 计算前n天的日期
        LocalDate dateBeforeDays = targetDate.minusMonths(2);
        final LambdaQueryWrapper<CodeHistory> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper
                .between(CodeHistory::getD, dateBeforeDays.toString(), dateScopedBo1.getEndDate())
                .eq(CodeHistory::getDm, dateScopedBo1.getDm())
                .orderByAsc(CodeHistory::getD);


        return codeHistoryMapper.selectList(queryWrapper);
    }

    /**
     * 获取历史代码
     */
    @Override
    @Transactional
    public List<CodeHistory> getByCodeListHistory(List<String> ListDm) {
        // 根据股票代码集合列表查询所有的股票代码 在传入时间内的价格
        LambdaQueryWrapper<CodeHistory> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(CodeHistory::getDm, ListDm);
        return codeHistoryMapper.selectList(queryWrapper);
    }

    /**
     * 根据持仓明细获取股票代码列表
     *
     * @param positionsDetail 持仓明细
     * @return 按照代码分组的列表
     */
    @Override
    @Transactional
    public Map<String, List<CodeHistory>> findAllDateByDm(List<Positions> positionsDetail) {
        final List<String> dmList = positionsDetail.stream().map(Positions::getDm).toList();
        final String minDate = positionsDetail.stream().map(Positions::getDate).min(String::compareTo).orElse("");
        if (minDate.isEmpty()) {
            GraceException.display("无法获取日期");
        }

        final LambdaQueryWrapper<CodeHistory> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.in(CodeHistory::getDm, dmList)
                .ge(CodeHistory::getD, minDate);
        final List<CodeHistory> codeHistoryList = codeHistoryMapper.selectList(queryWrapper);
        return codeHistoryList.stream().collect(Collectors.groupingBy(CodeHistory::getDm));
    }

    /**
     * 根据日期范围删除指定代码的历史数据。
     *
     * @param codeDateScopedBo 包含日期范围的代码日期范围对象。
     */
    @Override
    @Transactional
    public void deleteAllDateScope(CodeDateScopedBo codeDateScopedBo) {
        LambdaQueryWrapper<CodeHistory> queryWrapper = getHistoryLambdaQueryWrapper(codeDateScopedBo);
        codeHistoryMapper.delete(queryWrapper);
    }


    /**
     * 获取历史数据查询条件
     *
     * @param codeDateScopedBo 代码日期范围对象
     * @return LambdaQueryWrapper<CodeHistory>
     */
    private static LambdaQueryWrapper<CodeHistory> getHistoryLambdaQueryWrapper(CodeDateScopedBo codeDateScopedBo) {
        LambdaQueryWrapper<CodeHistory> queryWrapper = new LambdaQueryWrapper<>();
        if (codeDateScopedBo.getStartDate() != null && codeDateScopedBo.getEndDate() != null) {
            queryWrapper.between(CodeHistory::getD, codeDateScopedBo.getStartDate(), codeDateScopedBo.getEndDate());
        }

        // 如果指定了代码，则只删除该代码的历史数据
        if (codeDateScopedBo.getDm() != null && !codeDateScopedBo.getDm().isEmpty()) {
            queryWrapper.eq(CodeHistory::getDm, codeDateScopedBo.getDm());
        }
        return queryWrapper;
    }

    /**
     * 查询并计算所有代码历史数据。
     * 本方法首先查询数据库中所有的代码历史记录，然后根据代码库编号（dm）进行分组，
     * 对每组数据使用预定义的计算程序进行计算，最后将计算结果封装成 CalculateHistoryBo 对象列表返回。
     *
     * @return 包含所有计算历史的 CalculateHistoryBo 对象列表。
     */
    @Override
//    @Transactional
    public SumCalculateHistoryBo findAllDate(DateScopedBo dateScopedBo, CalculateProcedure calculateProcedure) {

        // 查询所有的代码历史记录
        final List<CodeHistory> codeHistories = getCodeHistories(dateScopedBo);

        // 根据代码库编号对代码历史记录进行分组
        final Map<String, List<CodeHistory>> collect = codeHistories.stream().collect(Collectors.groupingBy(CodeHistory::getDm));

        // 初始化计算历史业务对象列表
        List<CalculateHistoryBo> calculateHistoryBoList = new ArrayList<>();

        // 对每个代码库分组进行计算处理
        collect.forEach((dm, codeHistoryList) -> {
            // 根据代码历史记录集合提取并封装股票聚合信息
            final StockGatherVo stockGather = StockGatherUtils.getStockGather(codeHistoryList);
            // 使用计算程序1对股票聚合信息进行计算，得到计算历史业务对象
            final CalculateHistoryBo calculateHistoryBo = calculateProcedure.calculate(stockGather,
                    calculateProcedure);
            if (calculateHistoryBo != null && calculateHistoryBo.getProfitBoList() != null) {
                // 将计算历史业务对象添加到列表中
                calculateHistoryBoList.add(calculateHistoryBo);
            }
        });

        final SumCalculateHistoryBo sumCalculateHistoryBo = BasicNumerical.getSumCalculateHistoryBo(calculateHistoryBoList);
        getProfitGatherBo(sumCalculateHistoryBo, calculateProcedure);
        System.out.println("完成计算");
        // 返回计算历史业务对象列表
        return sumCalculateHistoryBo;
    }

    @Override
    public List<CodeHistory> getCodeHistories(DateScopedBo dateScopedBo) {
        final LambdaQueryWrapper<CodeHistory> queryWrapper = new LambdaQueryWrapper<>();
        if (dateScopedBo.getStartDate() != null && dateScopedBo.getEndDate() != null) {
            queryWrapper.between(CodeHistory::getD, dateScopedBo.getStartDate(), dateScopedBo.getEndDate());
        }
        //按照时间字段进行排序
        queryWrapper.orderByAsc(CodeHistory::getDm).orderByAsc(CodeHistory::getD);
        return codeHistoryMapper.selectList(queryWrapper);
    }


    /**
     * 这个方法不知道有什么问题如果不在循环前对数据进行清空会导致结果不对
     * 后面再来修改这个问题
     */
//    @Transactional
    public void getProfitGatherBo(SumCalculateHistoryBo sumCalculateHistoryBo, CalculateProcedure calculateProcedure) {
        // 按照日期分组的购买集合
        for (ProfitGatherBo yearBo : sumCalculateHistoryBo.getProfitGatherBoList()) {
            for (ProfitGatherBo monthBo : yearBo.getProfitGatherBosList()) {
                for (ProfitGatherBo dateBo : monthBo.getProfitGatherBosList()) {
                    final CodeDateScopedBo codeDateScopedBo = new CodeDateScopedBo();
                    // 定义日期格式
                    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
                    // 解析日期字符串为LocalDate对象
                    LocalDate targetDate = LocalDate.parse(dateBo.getDate(), formatter);
                    // 计算前n天的日期
                    LocalDate dateBeforeDays = targetDate.plusDays(50);
                    // 格式化结果日期
                    String endDate = dateBeforeDays.format(formatter);
                    String startDate = DateUtil.stringToDate(dateBo.getDate()).minusMonths(6).toString();
                    codeDateScopedBo.setStartDate(startDate);
                    codeDateScopedBo.setEndDate(endDate);

                    for (String dm : dateBo.getDms()) {
                        codeDateScopedBo.setDm(dm);
                        final List<CodeHistory> codeHistories = dateScopedList(codeDateScopedBo);
                        int useSize = 0;

                        for (int i = 0; i < codeHistories.size(); i++) {
                            final String d = codeHistories.get(i).getD();
                            if (d.equals(dateBo.getDate())) {
                                useSize = i - 1;
                                break;
                            }
                        }

                        // 股票指标集合
                        final StockGatherVo stockGather = StockGatherUtils.getStockGather(codeHistories);
                        CalculateHistoryBo calculateHistoryBo = new CalculateHistoryBo();
                        // 获取利润信息
                        calculateProcedure.getProfitBo(stockGather, useSize, calculateHistoryBo);

                        dateBo.setProfitCount(calculateHistoryBo.getProfitCount() + dateBo.getProfitCount());
                        dateBo.setFailCount(calculateHistoryBo.getFailCount() + dateBo.getFailCount());
                        dateBo.setStopLossCount(calculateHistoryBo.getStopLossCount() + dateBo.getStopLossCount());
                        dateBo.setTotalCount(calculateHistoryBo.getTotalCount() + dateBo.getTotalCount());
                        dateBo.setWinRate(getWinRate(dateBo.getProfitCount(), dateBo.getTotalCount()));

                    }

                    // 判断当天盈利概率是否达到 BasicParams.winRate 对应需要达到的胜率
                    if ((double) dateBo.getProfitCount() / dateBo.getTotalCount() > BasicParams.winRate) {
                        dateBo.setIsProfit(1);
                        monthBo.setProfitCounts(monthBo.getProfitCounts() + 1);
                    } else {
                        dateBo.setIsProfit(0);
                        monthBo.setFailCounts(monthBo.getFailCounts() + 1);
                    }

                    // 月累计
                    monthBo.setProfitCount(dateBo.getProfitCount() + monthBo.getProfitCount());
                    monthBo.setFailCount(dateBo.getFailCount() + monthBo.getFailCount());
                    monthBo.setStopLossCount(dateBo.getStopLossCount() + monthBo.getStopLossCount());
                    monthBo.setTotalCount(dateBo.getTotalCount() + monthBo.getTotalCount());
                    monthBo.setWinRate(getWinRate(monthBo.getProfitCount(), monthBo.getTotalCount()));

                }

                // 判断当月盈利概率是否达到 BasicParams.winRate 对应需要达到的胜率
                if ((double) monthBo.getProfitCounts() / (monthBo.getProfitCounts() + monthBo.getFailCounts()) > BasicParams.winRate) {
                    monthBo.setIsProfit(1);
                    yearBo.setProfitCounts(yearBo.getProfitCounts() + 1);
                } else {
                    monthBo.setIsProfit(0);
                    yearBo.setFailCounts(yearBo.getFailCounts() + 1);
                }

                // 年累计
                yearBo.setProfitCount(monthBo.getProfitCount() + yearBo.getProfitCount());
                yearBo.setFailCount(monthBo.getFailCount() + yearBo.getFailCount());
                yearBo.setStopLossCount(monthBo.getStopLossCount() + yearBo.getStopLossCount());
                yearBo.setTotalCount(monthBo.getTotalCount() + yearBo.getTotalCount());
                yearBo.setWinRate(getWinRate(yearBo.getProfitCount(), yearBo.getTotalCount()));
            }
        }
    }


    private double getWinRate(int profitCount, int totalCount) {
        if (totalCount == 0) {
            return 0;
        }
        final double v = (double) profitCount / totalCount * 100;
        final String format = String.format("%.2f", v);
        return Double.parseDouble(format);
    }


    /**
     * 计算指定日期可买入的数据
     *
     * @param codeDateScopedBo 根据时间范围查询符合条件的股票 如果有dm字段那么 查询对应代码对应符合的日期数据
     * @return 符合要求的股票代码
     */
    @Override
    @Transactional
    public List<String> calculateHistoryByDate(CodeDateScopedBo codeDateScopedBo) {
        System.out.println("开始" + DateUtil.stringToDateTime());
        final List<CodeHistory> historyList = dateScopedList(codeDateScopedBo);
        System.out.println("获取数据成功" + DateUtil.stringToDateTime());
        // 按代码进行分组
        final Map<String, List<CodeHistory>> collect = historyList.stream().collect(Collectors.groupingBy(CodeHistory::getDm));
        List<String> result = new ArrayList<>();
        collect.forEach((dm, codeHistoryList) -> {
            // 获取对应的股票指标
            final StockGatherVo stockGather = StockGatherUtils.getStockGather(codeHistoryList);
            // 计算该日期下指标是否符合条件
            final String calculate = NumericalProcedure1.Calculate(stockGather);
            if (calculate != null) {
                result.add(dm);
            }
        });
        System.out.println("完成获取" + DateUtil.stringToDateTime());
        return result;
    }

    /**
     * 根据日期范围和股票代码查询代码历史记录。
     *
     * @param codeDateScopedBo 包含开始日期、结束日期和股票代码的实体类。
     * @return 返回符合条件的代码历史记录列表。
     */
    @Override
//    @Transactional
    public List<CodeHistory> dateScopedList(CodeDateScopedBo codeDateScopedBo) {
        // 创建查询条件包装对象
        LambdaQueryWrapper<CodeHistory> queryWrapper = getHistoryLambdaQueryWrapper(codeDateScopedBo);

        // 根据查询条件获取代码历史记录列表
        return codeHistoryMapper.selectList(queryWrapper);
    }


    /**
     * 添加时间范围内数据
     * 数量很大先不要使用事务
     *
     * @param dateScopedBo 时间范围
     */
    @Override
    public void saveBatchAll(DateScopedBo dateScopedBo, List<Code> codeList) {
        String type = "dn";
        for (Code code : codeList) {
            final List<CodeHistory> historyList = getHistoryList(dateScopedBo, code.getDm(), type);
            if (historyList.isEmpty()) {
                continue;
            }
            for (CodeHistory codeHistory : historyList) {
                try {
                    codeHistoryMapper.insert(codeHistory);
                } catch (Exception e) {
                    log.error("插入数据失败", e);
                    continue;
                }
            }
        }
    }

    /**
     * 获取最新的历史数据
     *
     * @param dm 查询代码
     * @return
     */
    @Override
    public void setNewHistory(String dm) {
        // 构造请求URL，包括数据列表的基础URL、代码类型和许可证信息。
        String url = GlobalParameter.NEW_CODE_LIST + dm + "/" + GlobalParameter.LICENCE;
        // 发起HTTP GET请求，获取远程服务返回的JSON字符串。
        String jsonString = HttpUtil.get(url);
        // 初始化一个列表，用于存储解析出的历史数据对象。
        String error_message = "API calls quota exceeded";
        if ("null".equals(jsonString)) {
            return;
        }

        String date = DateUtil.getToDate();
        log.info(dm + "========有无限制住========：" + jsonString.contains(error_message));
        // 在时间段内调用超出限制等待1秒钟后重新调用
        if (jsonString.contains(error_message) || jsonString.contains("API calls quota exceeded! maximum admitted 300 per Minute.")) {
            try {
                log.info("请求频率过快进入等待：" + jsonString);
                log.info(DateUtil.stringToDateTime() + "开始时间");
                Thread.sleep(60000);
                log.info(DateUtil.stringToDateTime() + "开始请求======================================");
                jsonString = HttpUtil.get(url);
                log.info(DateUtil.stringToDateTime() + "重新请求数据======================================");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        // 将JSON字符串解析为JSONArray对象。
        final JSONObject entries = new JSONObject(jsonString);

        final CodeHistory codeHistory = new CodeHistory();
        codeHistory.setDm(dm);
        codeHistory.setD(date);
        codeHistory.setO(entries.getBigDecimal("o"));
        codeHistory.setH(entries.getBigDecimal("h"));
        codeHistory.setL(entries.getBigDecimal("l"));
        codeHistory.setC(entries.getBigDecimal("p"));
        codeHistory.setV(entries.getBigDecimal("v"));
        codeHistory.setE(entries.getBigDecimal("cje"));
        codeHistory.setZf(entries.getBigDecimal("zf"));
        codeHistory.setHs(entries.getBigDecimal("hs"));
        codeHistory.setZd(entries.getBigDecimal("pc"));
        codeHistory.setZde(entries.getBigDecimal("ud"));
        codeHistoryMapper.insert(codeHistory);
    }


    /**
     * 根据给定的时间范围和代码类型，从远程服务获取历史数据列表。
     * 这个方法解释了为什么需要获取历史数据列表（即根据特定的时间范围和代码类型），以及它是如何做到的（通过调用HTTP服务）。
     *
     * @param dateScopedBo 包含开始和结束日期的时间范围，用于筛选数据。
     * @param dm           代码类型标识，用于指定获取哪种类型的历史数据。
     * @param type         数据类型标识，用于指定获取的历史数据的具体类型。
     * @return 返回符合条件的历史数据列表，列表中的每个元素代表了一天的数据。
     */
    @Override
    public List<CodeHistory> getHistoryList(DateScopedBo dateScopedBo, String dm, String type) {
        // 构造请求URL，包括数据列表的基础URL、代码类型和许可证信息。
        String url = GlobalParameter.CODE_LIST + dm + "/" + type + "/" + GlobalParameter.LICENCE;
        // 发起HTTP GET请求，获取远程服务返回的JSON字符串。
        String jsonString = HttpUtil.get(url);
        // 初始化一个列表，用于存储解析出的历史数据对象。
        List<CodeHistory> listCode = new ArrayList<>();
        String error_message = "API calls quota exceeded";

        if (jsonString.contains("error '404 Not Found")) {
            return listCode;
        }
        if (jsonString.contains("Requested resource not found.")) {
            return listCode;
        }
        if ("null".equals(jsonString)) {
            return listCode;
        }
        log.info(dm + "========有无限制住========：" + jsonString.contains(error_message));

        String errText = "请求超过证书限流";
        // 在时间段内调用超出限制等待1秒钟后重新调用
        if (jsonString.contains(errText) || jsonString.contains(error_message) || jsonString.contains("API calls quota exceeded! maximum admitted " +
                "300 per Minute.")) {
            try {
                log.info("请求频率过快进入等待：" + jsonString);
                log.info(DateUtil.stringToDateTime() + "开始时间");
                Thread.sleep(60000);
                log.info(DateUtil.stringToDateTime() + "开始请求======================================");
                jsonString = HttpUtil.get(url);
                log.info(DateUtil.stringToDateTime() + "重新请求数据======================================");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        try {
            // 将JSON字符串解析为JSONArray对象。
            final JSONArray jsonArray = new JSONArray(jsonString);
            // 遍历JSON数组，对每个元素解析为CodeHistory对象，并根据时间范围进行筛选。
            for (int i = 0; i < jsonArray.size(); i++) {
                final CodeHistory codeHistory = new CodeHistory();
                // 从JSON对象中提取各个字段的值，并设置到CodeHistory对象中。
                // 将Object转换为JSONObject以访问其内部的键值对
                final JSONObject obj = jsonArray.getJSONObject(i);
                codeHistory.setDm(dm);
                codeHistory.setD(obj.getStr("d"));
                codeHistory.setO(obj.getBigDecimal("o"));
                codeHistory.setH(obj.getBigDecimal("h"));
                codeHistory.setL(obj.getBigDecimal("l"));
                codeHistory.setC(obj.getBigDecimal("c"));
                codeHistory.setV(obj.getBigDecimal("v"));
                codeHistory.setE(obj.getBigDecimal("e"));
                codeHistory.setZf(obj.getBigDecimal("zf"));
                codeHistory.setHs(obj.getBigDecimal("hs"));
                codeHistory.setZd(obj.getBigDecimal("zd"));
                codeHistory.setZde(obj.getBigDecimal("zde"));

                // 检查当前数据的时间是否在指定的时间范围内，如果是，则添加到结果列表中。
                if (codeHistory.getD().compareTo(dateScopedBo.getStartDate()) >= 0 && codeHistory.getD().compareTo(dateScopedBo.getEndDate()) <= 0) {
                    listCode.add(codeHistory);
                }
            }
            // 返回筛选后的历史数据列表。
            return listCode;
        } catch (Exception e) {
            log.info("========json========：" + jsonString);
            log.error("解析数据失败", e);
        }

        return null;
    }


}
